<!DOCTYPE html>
<html>
<head>
  <title>Google I/O</title>
  <meta charset='utf-8' />
  <script src='slides/slides.js'></script>

  <script src="codemirror2/lib/codemirror.js"></script>
  <link rel="stylesheet" href="codemirror2/lib/codemirror.css">
  <script src="codemirror2/mode/clike/clike.js"></script>
  <link rel="stylesheet" href="codemirror2/mode/clike/clike.css">

  <script src="Stats.js"></script>
    
</head>
  
<style>
/* http://www.w3.org/TR/css3-background/#the-background-position doesn't work yet,
   so use an explicit image for the good/bad logos */
.slides > article.bad > img {
  height: 254px;
  position:absolute;
  z-index: -1;
  right: 4px;
  bottom: 96px;
}

.slides > article.good > img {
  height: 264px;
  position:absolute;
  z-index: -1;
  right: 24px;
  bottom: 104px;
}

article.hi img {
  position: absolute;
  left: 161px;
  bottom: 92px;
  height: 532px;
}

article.hi h1 {
  position: absolute;
  top: -120px;
  left: 450px;
  font-size: 96pt;
}

article.myfill img {
  position:relative;
  left: 80px;
  width: 616;  /* 685 * 0.9 */
  height: 635px; /* 706 * 0.9 */
  z-index: -1;
}

a.demolink {
  font-size: 30px;
  font-weight: normal;
  letter-spacing: 0;
}

p.huge {
  display: block;
  font-size: 72pt;
  line-height: 72pt;
}
p.aside {
  position: absolute;
  bottom: 150px;
  font-size: 18px;
  letter-spacing: 0;
}

button {
  font-size: 14px;
}
</style>

<body style='display: none'>

  <!--<section class='slides layout-regular'>-->
  <!--<section class='slides layout-widescreen'>-->
  <!--<section class='slides layout-faux-widescreen'>-->
  <section class="slides layout-regular">
    
    <article class='biglogo'>
    </article>

    <article class='hi'>
      <h1>
        Hi!
      </h1>
      <p>
        <img src='images/android.png'>
      </p>
    </article>

    <article>
      <h1>
        3D Graphics on Android:<br>
        Lessons from Google Body
      </h1>
      <p>
        Nico Weber
        <br>
        May 11, 2011
      </p>
    </article>

    <article>
      <h2>
        Demo<br>
        <a href="http://bodybrowser.googlelabs.com/" class="demolink"
            >bodybrowser.googlelabs.com</a>
      </h2>
    </article>

<!-- TODO: Decide if this should be inline or in a new tab.
    <article class='fill'>
      <iframe src='http://bodybrowser.googlelabs.com/body.html'></iframe>
    </article>
-->

    <article>
      <h3>Google Body</h3>
      <ul class="build">
        <li>Web app
        <li>Uses WebGL
          <ul class="build">
            <li>Chrome
            <li>Firefox 4
            <li>WebKit nightly
            <li>Opera 11 preview
            <li>…not yet in the Android browser
          </ul>
        </li>
      </ul>
    </article>

    <article>
      <h2>
        Demo<br>
        <a href="https://market.android.com/details?id=com.google.android.apps.body" class="demolink"
            >Google Body for Android</a>
      </h2>
    </article>

    <article>
      <p class="huge">I&rsquo;m mostly clueless.</p>
    </article>

    <article>
      <p class="huge">Ideally, so are you.</p>
      <p class="aside">
        <em>Completely</em> clueless? Read <a href="http://code.google.com/p/gdc2011-android-opengl/">http://code.google.com/p/gdc2011-android-opengl/</a>
      </p>
    </article>


    <article>
      <h3>Google Body for Android</h3>
      <ul class="build">
        <li>Native Java app
        <li>Uses OpenGL ES 2.0
      </ul>
    </article>


    <article>
      <h3>Agenda</h3>
      <div class="build">
      </div>
      <ol start="0" class="build">
        <li>OpenGL ES 2.0
        <li>GPUs
        <li>Textures
        <li>Vertex Buffer Objects
        <li>Reading data
        <li>Performance tweaking
      </ol>
    </article>


    <article>
      <h2>Part 0: OpenGL ES 2.0</h2>
    </article>

    <article>
      <h3>OpenGL</h3>
      <div class="build">
      <pre>
glBegin(GL_TRIANGLES);
  glVertex3f(0, 0, 0);
  glVertex3f(1, 0, 0);
  glVertex3f(0, 1, 0);
glEnd();
</pre>
      <p>The John Carmack of 3d apis.</p>
      <p>Roughly the same age, too.</p>
      </div>
    </article>

    <article>
      <h3>OpenGL ES</h3>
      <ul class="build">
        <li>Simplified OpenGL, for mobile devices.
        <li>Fewer ways to do things (e.g. no <code>glBegin()</code>)
        <li>OpenGL ES 1 has a fixed function pipeline.
        <li>OpenGL ES 2 supports programmable vertex and fragment shaders.
        <li>Android supports both.
      </ul>
    </article>

    <article>
      <h3>(WebGL)</h3>
      <ul>
        <li>(WebGL is OpenGL ES 2.0 for web apps.)
        <li>(Used by Body/Web, not by Body/Android.)
      </p>
    </article>

    <article>
      <p>
        <!--<img class="centered" src="http://chart.apis.google.com/chart?&cht=p&chs=660x450&chd=t:2.7,3.5,27.2,63.9,0.8,1.7,0.2&chl=Android%201.5|Android%201.6|Android%202.1|Android%202.2|Android%202.3|Android%202.3.3|Android%203.0&chco=c4df9b,6fad0c">-->
        <img class="centered" src="images/android-all.png">
      </p>
      <div class="source">Source: <a href="http://developer.android.com/resources/dashboard/platform-versions.html">developer.android.com/resources/dashboard/platform-versions.html</a></div>
    </article>

    <article>
      <p>
        <!--<img class="centered" src="http://chart.apis.google.com/chart?&cht=p&chs=660x450&chd=t:2.7,3.5,27.2,63.9,0.8,1.7,0.2&chl=||Android%202.1||||&chco=c4df9b,6fad0c">-->
        <img class="centered" src="images/android-2.1.png">
      </p>
    </article>

    <article>
      <p>
        <!--<img class="centered" src="http://chart.apis.google.com/chart?&cht=p&chs=660x450&chd=t:2.7,3.5,27.2,63.9,0.8,1.7,0.2&chl=|||Android%202.2|||&chco=c4df9b,6fad0c">-->
        <img class="centered" src="images/android-2.2.png">
      </p>
    </article>

    <article>
      <p>
        <!--<img class="centered" src="http://chart.apis.google.com/chart?&cht=p&chs=660x450&chd=t:2.7,3.5,27.2,63.9,0.8,1.7,0.2&chl=||||Android%202.3|Android%202.3.3|&chco=c4df9b,6fad0c">-->
        <img class="centered" src="images/android-2.3.png">
      </p>
    </article>

<!--
    <article>
      <h3>Looking back</h3>
      <p>
        <img class="centered" src="http://chart.apis.google.com/chart?&cht=lc&chs=660x450&chxt=x,x,y,r&chxr=0,0,12|1,0,12|2,0,100|3,0,100&chxl=0%3A|10/01|10/15|11/01|11/15|12/01|12/15|01/01|01/15|02/01|02/15|03/01|03/15|04/01|1%3A|2010||||||2011||||||2011|2%3A|0%25|25%25|50%25|75%25|100%25|3%3A|0%25|25%25|50%25|75%25|100%25&chxp=0,0,1,2,3,4,5,6,7,8,9,10,11,12&chxtc=0,5&chd=t:99.9,99.9,100.0,99.9,99.8,99.7,100.0,99.9,99.9,99.9,100.0,99.8,99.7|90.2,91.1,92.0,92.7,93.4,94.1,95.2,95.6,96.0,96.3,96.7,96.8,97.0|73.8,75.3,77.4,79.6,82.2,84.4,87.2,88.3,89.7,90.5,91.5,92.0,93.5|33.4,34.5,37.1,40.5,44.3,47.7,51.8,54.3,58.3,59.7,61.5,63.0,66.3|0.0,0.0,0.0,0.0,0.0,0.0,0.4,0.6,0.7,0.8,1.1,1.7,2.5|0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,0.0,1.0,1.7&chm=b,c3df9b,0,1,0|tAndroid%201.6,689326,1,0,15,,t::-5|b,b4db77,1,2,0|tAndroid%202.1,547a19,2,0,15,,t::-5|b,a5db51,2,3,0|tAndroid%202.2,3f5e0e,3,0,15,,t::-5|b,96dd28,3,4,0|b,83c916,4,5,0|B,6fad0c,5,6,0&chg=7,25&chdl=Android%201.5|Android%201.6|Android%202.1|Android%202.2|Android%202.3|Android%202.3.3&chco=add274,9dd14f,8ece2a,7ab61c,659b11,507d08">
      </p>
    </article>
-->


    <article>
      <h3><code>GLSurfaceView</code></h3>
    </article>

    <article>
      <h3><code>GLSurfaceView</code></h3>
      <pre>
  // In your Activity
  @Override
  public void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      mView = new GLSurfaceView(this);
      mView.setEGLContextClientVersion(2);
      mView.setRenderer(new MyFineRenderer());
      setContentView(mView);
  }

  @Override
  protected void onPause() { super.onPause(); mView.onPause(); }

  @Override
  protected void onResume() { super.onResume(); mView.onResume(); }
</pre>
    </article>

    <article>
      <h3><code>GLSurfaceView</code></h3>
      <pre>
&lt;!-- In AndroidManifest.xml --&gt;
&lt;uses-feature
  android:glEsVersion="0x00020000"
  android:required="true" /&gt;
</pre>
    </article>


<script>
function runCode(editor, connection) {
  var rawCode = editor.getValue();
  // Doesn't allow nested blocks, but that's good enough.
  var drawCode = /onDrawFrame[^{]*{([^}]+)/.exec(rawCode)[1];
  var createCode = /onSurfaceCreated[^{]*{([^}]+)/.exec(rawCode);
  var code = {
      'DrawCode': drawCode
      };
  if (createCode && createCode.length > 1)
    code.CreateCode = createCode[1];
  code = JSON.stringify(code);
  console.log('sending ' + code);
  connection.send(code);
}

function updateButton(buttonId, editor, connection) {
  var button = document.getElementById(buttonId);
  var visible = connection && connection.readyState == connection.OPEN && editor;
  if (visible) button.style.visibility = '';
  else button.style.visibility = 'hidden';
}


var cm;
var conn;
function installCM(pr) {
  if (!cm) {
    cm = CodeMirror(
        function(elt) { console.log(elt); pr.parentNode.replaceChild(elt, pr); },
        { 'mode': 'text/x-java', 'value': pr.textContent, 'indentUnit': 4 });
    updateButton('playButton', cm, conn);
  }
}

function onSlideEnter() {
  conn = new WebSocket('ws://localhost:12345/adb');
  conn.onopen = function() {
    updateButton('playButton', cm, conn);
  }
  conn.onmessage = function(event) {
    var data = JSON.parse(event.data);
    if (data.Kind != 'fps') {
      document.getElementById('messagespan').innerHTML = data.Text;
    }
  }
}

function onSlideLeave() {
  if (conn) {
    conn.disconnect();
    console.log('disconnected');
  }
}
</script>

<!--
Insert into create:
      mView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
      glClearColor(0.8f, 0.8f, 0.8f, 1.0f);

Replace in draw with:
      glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
-->
    <article onslideenter="onSlideEnter();" onslideleave="onSlideLeave();">
      <h3><code>Renderer</code></h3>
      <pre id="rendererTextarea" onclick="installCM(this)">
import static android.opengl.GLES20.*;
public class MyFineRenderer implements GLSurfaceView.Renderer {
  @Override
  public void onSurfaceCreated(GL10 unused, EGLConfig config) {
  }

  @Override
  public void onDrawFrame(GL10 unused) {
      // Do actual drawing, calculate fps, etc
  }

  @Override
  public void onSurfaceChanged(GL10 unused, int w, int h) {}
}</pre>
      <div class="source">
        <button onclick="runCode(cm, conn);" id="playButton" style="visibility:hidden">Run</button>
        <span id="messagespan"></span>
      </div>
    </article>


    <article>
      <h3>Communicate UI thread &rarr; renderer thread</h3>
      <p>Use <code>GLSurfaceView.queueEvent</code>:</p>
      <pre>
// In Activity, on UI thread.
void onClick(DialogInterface dialog, int item) {
  mGLView.queueEvent(new Runnable() {
      public void run() {
        // Runs on renderer thread.
        mGLView.getRenderer().setDrawsSomeFunkySelection();
      }});
}
</pre>
    </article>

    <article>
      <h3>Communicate renderer thread &rarr; UI thread</h3>
      <p>Use <code>Activity.runOnUiThread</code>:</p>
      <pre>
// on renderer thread
void ohHeyDoneUploadingData() {
  mActicity.runOnUiThread(new Runnable() {
      public void run() {
        // Runs on UI thread.
        mActivity.setSearchBoxEnabledAndWhatNot();
      }});
}
</pre>
    </article>

    <article class="good">
      <img src="images/android-happy.png">
      <h3>Advice: Use <code>GLSurfaceView</code></h3>
      <ul>
        <li>Easy to use
        <li>Dedicated renderer thread
        <li>It's well-tested, you don't run into <a href="http://code.google.com/p/replicaisland/source/browse/trunk/src/com/replica/replicaisland/GLSurfaceView.java?spec=svn6&r=6#1269">scary bugs</a>
      </ul>
    </article>

    <article class="bad">
      <img src="images/android-broken.png">
      <h3>Beware: <code>GLSurfaceView</code> loses its GL context often</h3>
      <ul class="build">
        <li>…every <code>onPause()</code>
        <li>Make loading data fast.
        <li>3.0: <code>setPreserveEGLContextOnPause()</code>, but not guarateed to help
      </ul>
    </article>


    <article>
      <h2>
        Part 1: Rough mental model of GPUs
      </h2>
    </article>

    <article class="myfill nobackground">
      <p>
        <img src="images/gpu0.png">
      </p>
    </article>

    <article class="myfill nobackground">
      <p>
        <img src="images/gpu1.png">
      </p>
    </article>

    <!--<article class="myfill nobackground">
      <ul>
        <li>Batch draw calls
        <li>Keep data small
      </ul>
    </article>-->


    <article>
      <h2>
        Part 2: Textures
      </h2>
    </article>

    <article>
      <h3>
        OpenGL 101: Don't upload textures every frame
      </h3>
      <pre>
void onDrawFrame(GL10 unused) {
  glTexImage2D(..., GL_RGBA, ..., texData);  // DON'T
  drawModel()
}
</pre>
      <pre style="position:relative; top:-20px;">
// Better:
void onSurfaceCreated(GL10 unused, EGLConfig config) {
  int[] texture = { 0 };
  glGenTextures(1, texture, 0);
  glBindTexture(GL_TEXTURE_2D, texture[0]);
  glTexImage2D(..., GL_RGBA, ..., texData);
}
void onDrawFrame(GL10 unused) {
  glBindTexture(GL_TEXTURE_2D, texture[0]);
  drawModel();
}
</pre>
    </article>

    <article class="good">
      <img src="images/android-happy.png">
      <h3>
        Advice: Use ETC for RGB texture compression
      </h3>
      <p>
        Save 75% memory, or have twice the texture resolution for the same
        memory.
      </p>
      <!--<p>
        If you need alpha textures, sideload per architecture.
http://developer.motorola.com/docstools/library/understanding-texture-compression/
      </p>-->
      <p>
        <code>android-sdk/tools/etc1tool</code> converts pngs to pkm
      </p>
      <pre>
&lt;supports-gl-texture
    android:name="GL_OES_compressed_ETC1_RGB8_texture" /&gt;</pre>
    </article>

    <article>
      <h3>ETC loading code</h3>
      <pre>
// ETC1Util requires Android 2.2+
ETC1Texture t = ETC1Util.createTexture(
  getResources().openRawResource(
      R.raw.mytexture));  // I/O thread

ETC1Util.loadTexture(..., t);  // GL thread</pre>
    </article>

    <article class="bad">
      <img src="images/android-broken.png">
      <h3>
        Beware: Only ETC textures with w, h being a multiple of 4 look right on PowerVR
      </h3>
      <ul>
        <li>Most textures are POTs anyway
        <li>Make HUD texture sizes multiples of 4
      </ul>
    </article>

    <article>
      <h2>
        Part 3: VBOs
      </h2>
    </article>

    <article>
      <h3>
        OpenGL ES 101: Don't upload vertex data every frame
      </h3>
      <pre>
void onDrawFrame(GL10 unused) {  // NO NO NO
  glVertexAttribPointer(..., GL_FLOAT, 3, ..., attribData);
  glDrawElements(GL_TRIANGLES, ..., indexData);
}
</pre>
    </article>

    <article>
      <h3>
        OpenGL ES 101: Don't upload vertex data every frame
      </h3>
      <pre>
// Better (just attribs shown, indices part looks similar):
void onSurfaceCreated(GL10 unused, EGLConfig config) {
  attribVBO = glGenBuffers(1);
  glBindBuffer(GL_ARRAY_BUFFER, attribVBO);
  glBufferData(..., attribData ...);
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexVBO);
  glBufferData(..., indexData ...);
}
void onDrawFrame(GL10 unused) {
  glBindBuffer(GL_ARRAY_BUFFER, attribVBO);
  glVertexAttribPointer(..., GL_FLOAT, 3, ..., 0); // !
  glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexVBO);
  glDrawElements(GL_TRIANGLES, ..., 0);  // !
}
</pre>
    </article>


    <article class="bad">
      <img src="images/android-broken.png">
      <h3>
        Beware: OpenGL ES 2.0 Java bindings incomplete on Android 2.2
      </h3>
      <p>
        Annoying, but easy to fix.
      </p>
      <p>
        …if you&rsquo;re familiar with the NDK
      </p>
    </article>


    <article>
      <pre>
// src/com/example/yourapp/GLES20Fix.java
public class GLES20Fix {
  native public static void glDrawElements(
      int mode, int count, int type, int offset);

  native public static void glVertexAttribPointer(
      int index, int size, int type, boolean normalized,
      int stride, int offset);

  private GLES20Fix() {}
  static {
      System.loadLibrary("GLES20Fix");
  }
}
</pre>
    </article>

    <article>
      <pre>
// jni/GLES20Fix.c
#include &lt;jni.h&gt;
#include &lt;GLES2/gl2.h&gt;

// |extern "C"| if you're using C++
void Java_com_example_io_GLES20Fix_glDrawElements(
        JNIEnv *env, jclass c, jint mode,
        jint count, jint type, jint offset) {
    glDrawElements(mode, count, type, (void*) offset);
}

// Same for other method
</pre>
    </article>

    <article>
      <pre>
# jni/Android.mk
LOCAL_PATH:= $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := GLES20Fix
LOCAL_SRC_FILES := GLES20Fix.c
LOCAL_LDLIBS    := -lGLESv2

include $(BUILD_SHARED_LIBRARY)
</pre>
      <pre>
cd jni
ndk-build
# Then do a clean build in Eclipse to make it pick up the new .so
</pre>
    </article>

<!--
    <article>
      <ul>
        <li>Use same technique for 2.1.
        <li>And for extensions:
      </ul>
      <pre>
#include &lt;dlfcn.h&gt;
typedef unsigned long long (*PGetSystemTimeNV)(void);

jlong Java_com_google_android_apps_body_GLES20Fix_eglGetSystemTimeNV(
  JNIEnv *env, jclass c) {
  static PGetSystemTimeNV gst = 0;
  if (!gst) {
    gst = dlsym(RTLD_DEFAULT, "eglGetSystemTimeNV");
    // Check glGetString(GL_EXTENSIONS) /
    // eglQueryString(display, EGL_EXTENSIONS) first!
    // eglGetProcAddress() isn't in Android's egl.h, and
    // extensions aren't officially supported.
  }
  return (*gst)();
}
</pre>
    </article>
-->





    <article>
      <h2>
        Part 4: Filling direct <code>ByteBuffer</code>s
      </h2>
    </article>

    <article>
      <h3>
        Direct <code>ByteBuffer</code>s
      </h3>
      <ul>
        <li>The way to get data into and out of OpenGL
        <li>&ldquo;Raw C memory&rdquo;
        <li>Element-wise access is slow!
      </ul>
    </article>

    <article>
      <h3>Wrong</h3>
      <pre>
// DO NOT DO THIS.
InputStream is = getResources().openRawResource(R.raw.myresource);
    // Or |getAssets().open("my.resource")|
int size = is.available(), v;
ByteBuffer b = ByteBuffer.allocateDirect(size);
while ((v = is.read()) != -1)
    b.put(v);  // OMG ELMENT-WISE ACCESS, SLOW
// A buffer has an internal "current index"
b.rewind();
</pre>
    </article>

    <article>
      <h3>Better</h3>
      <pre>
// This is ok.

// In a real app, use a block size and a loop.
byte[] buff = new byte[size];
is.read(buff);
b.put(buff);
</pre>
    </article>

    <article>
      <h3>Faster still</h3>
      <ul>
        <li>Bundles resources are compressed by default
        <li>Certain magic extensions aren&rsquo;t, e.g. &ldquo;png&rdquo;, &ldquo;jpg&rdquo;, &ldquo;jet&rdquo;
            <ul><li>Use e.g. &ldquo;jet&rdquo; for uncompressed files</ul>
        <li>You can get a file handle to uncompressed resources!
      </ul>
    </article>

    <article>
      <h3>Faster still</h3>
      <pre>
AssetFileDescriptor ad = getAssets().openFd("myfile.jet");
    // or |getResources().openRawResourceFd(id)|
FileInputStream fis = ad.createInputStream();
FileChannel fc = fis.getChannel();
// Always direct.
MappedByteBuffer b = fc.map(
    MapMode.READ_ONLY,
    ad.getStartOffset(),
    ad.getLength());
long size = ad.getLength();
// upload b here
</pre>
    </article>

    <article class="bad">
      <img src="images/android-broken.png">
      <h3>
        Beware: <code>ByteBuffer.allocateDirect()</code> overallocates
      </h3>
      <p>
        Tears. Not much you can do about it. Keep your buffers small.
      </p>
      <p>
        Or use NDK &amp; own bytebuffer-less bindings for upload.
      </p>
    </article>

    <article class="bad">
      <img src="images/android-broken.png">
      <h3>
        Beware: Compressed files can be max 1MB uncompressed on &le; Android 2.2
      </h3>
      <p>
        Tears. Split files.
      </p>
      <p>
        Or roll your own compression.
      </p>
    </article>



    <article>
      <h2>
        Part 5: Performance
      </h2>
    </article>

    <article>
      <p class="huge">
        Measure.
      </p>
    </article>

<script>
var cm2;
var conn2;
var stats;
var lastTimestamp = 0;

function installCM2(pr) {
  if (!cm2) {
    cm2 = CodeMirror(
        function(elt) { console.log(elt); pr.parentNode.replaceChild(elt, pr); },
        { 'mode': 'text/x-java', 'value': pr.textContent, 'indentUnit': 4 });
    updateButton('playButton2', cm2, conn2);
  }
}

window.requestAnimFrame = (function(){
      return  window.requestAnimationFrame       || 
              window.webkitRequestAnimationFrame || 
              window.mozRequestAnimationFrame    || 
              window.oRequestAnimationFrame      || 
              window.msRequestAnimationFrame     || 
              function(/* function */ callback, /* DOMElement */ element){
                window.setTimeout(callback, 1000 / 30);
              };
    })();

var pendingData = [];

function paintData() {
  stats.update(pendingData);
  pendingData = [];
}

function handleMessage(data) {
  if (data.Kind != 'fps') {
    document.getElementById('messagespan2').innerHTML = data.Text;
  } else {
    if (data.Timestamp > lastTimestamp) {
      lastTimestamp = data.Timestamp;
      pendingData.push(data.Fps);
      //console.log(data.Fps);
      if (pendingData.length === 1) // TODO: needed?
        requestAnimFrame(paintData);
    }
  }
}

function onSlideEnter2() {
  conn2 = new WebSocket('ws://localhost:12345/adb');
  conn2.onopen = function() {
    updateButton('playButton2', cm2, conn2);
  }
  conn2.onmessage = function(event) {
    var data = JSON.parse(event.data);
    handleMessage(data);
  }
}

function runCode2(editor, connection) {
  runCode(editor, connection);
  if (!stats) {
    stats = new Stats();
    document.getElementById('playButton2').parentNode.parentNode.appendChild(stats.domElement);
    // Debug:
    /*setInterval(function() {
        var n = 1;
        for (var i = 0; i < n; ++i)
          handleMessage({
              'Kind':'fps',
              'Fps':Math.random()*10 + 20,
              'Timestamp': new Date().getTime() + i/n
          });
      }, 200);*/
  }
}

function onSlideLeave2() {
  if (conn2) {
    conn2.disconnect();
    console.log('disconnected');
  }
}
</script>
    <article onslideenter="onSlideEnter2();" onslideleave="onSlideLeave2();">
      <h3>Demo</h3>
      <pre id="rendererTextarea" onclick="installCM2(this)">
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
    //GLES20Fix.setPreserve();
}
public void onDrawFrame(GL10 unused) {
    for (int i = 0; i < 7; ++i)
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}</pre>
      <div class="source">
        <button onclick="runCode2(cm2, conn2);" id="playButton2" style="visibility:hidden">Run</button>
        <span id="messagespan2"></span>
      </div>
    </article>

    <article class="bad">
      <!--<img src="images/android-broken.png">-->
      <h3>
        Beware: Double-buffering skews measurement
      </h3>
      <p>
      </p>
      <ul>
        <li>Tegra2 does this.
        <li>Frame time multiple of 1/60 s.
        <li>Can force triple-buffering with this hack:
      </ul>
      <pre>
#include &lt;egl/egl.h&gt;  // -lEGL in Android.mk

void Java_com_example_io2011_GLES20Fix_setPreserve() {
  #define EGL_SWAP_BEHAVIOR               0x3093
  #define EGL_BUFFER_PRESERVED            0x3094

  eglSurfaceAttrib(  // Possibly during dev only, for measuring.
      eglGetCurrentDisplay(), eglGetCurrentSurface(EGL_DRAW),
      EGL_SWAP_BEHAVIOR, EGL_BUFFER_PRESERVED);
}
</pre>
    </article>


    <article>
      <h3>Recap</h3>
        <ul>
          <li>Use VBOs
          <li>Use indexed geometry
          <li>Use interleaved vertices
          <li>Use "small" attributes
           <ul><li>GL_OES_vertex_half_float widely supported</ul>
          <li>Use time-dependent animation
        </ul>
    </article>

    <article>
      <h3>Fragment processor bound</h3>
        <p>
        How to know: Reducing texture sizes doesn't help, but <code>glViewport(0, 0, 1, 1)</code> does
        <ul>
          <li>Move work to the vertex shader
          <li>Consider replacing calculations with texture look-ups
          <li>Use pre-baked lighting
          <li>Use backface culling
          <li>Do not use "discard"
        </ul>
        </p>
    </article>

    <article>
      <h3>Texture fetch bound</h3>
        <p>
        How to know: Using much smaller textures and changing nothing else is way faster.
        <ul>
          <li>Use texture compression
          <li>Use mipmaps
          <li>Use smaller / fewer textures
        </ul>
        </p>
    </article>

    <article>
      <h3>Vertex processor bound</h3>
        <p>
        How to know: Lower-res model that covers about the same number of pixels is way faster / small viewport isn&rsquo;t
        <ul>
          <li>Use fewer or smaller attributes
          <li>Use fewer or smaller varyings
            <ul><li><code>varying lowp vec4 color;</code></ul>
          <li>Do some calculation per-model
          <li>E.g. lighting in model space
          <li>Use LOD
          <li>Do high-level object culling on the CPU
        </ul>
        </p>
    </article>

    <article>
      <h3>CPU bound</h3>
        <p>
        How to know: <code>traceview</code> and <code>top</code> shows your code as expensive.
        <ul>
         <li>CPU should only send data to GPU
         <li>Batch draw calls
         <li>Texture atlases
         <li>Tristrips: Degenerate triangles
         <li>Don't allocate memory in your inner loop
         <li>If all else fails, look at NDK
        </ul>
        </p>
    </article>


    <article> 
      <h3>Thank you!</h3> 
      <ul class="build"> 
        <li>Code, Slides, etc at<br><a href="http://code.google.com/p/io2011-opengl-for-android/">http://code.google.com/p/io2011-opengl-for-android/</a> 
        <li>Leave feedback at <a href="http://goo.gl/tUMU4">http://goo.gl/tUMU4</a>
        <li>Questions?
      </ul> 
      <p class="aside">#io2011 #android</p>
    </article> 


</body>
</html>
